#include "process_helper.h"

#include <dirent.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/stat.h>  // For chmod()
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <regex>  // NOLINT
#include <string>
#include <cstring>
#include <utility>
#include <vector>

// RAII class to automatically close file on destruction
class FileCloser {
 public:
  explicit FileCloser(std::ifstream& file) : file_(file) {
  }
  ~FileCloser() {
    if (file_.is_open()) {
      file_.close();
    }
  }
  std::ifstream& get() const {
    return file_;
  }

 private:
  std::ifstream& file_;
};

/**
 * @brief RAII wrapper for DIR* to ensure proper closing
 */
class DirCloser {
 public:
  explicit DirCloser(DIR* dir) : dir_(dir) {
  }
  ~DirCloser() {
    if (dir_) {
      closedir(dir_);
    }
  }
  DIR* get() const {
    return dir_;
  }  // Provide a getter for the internal DIR pointer
 private:
  DIR* dir_;
};

/**
 * @brief Function to check if the process is not in stopped state
 *
 * @param [in] pid - pid find by process name
 * @return true - stopped; false - not stopped
 */
bool IsProcessStopped(pid_t pid) {
  std::ifstream statusStream(std::string("/proc/") + std::to_string(pid) +
                             "/status");
  std::string line;
  std::regex stateRegex("^State:\\s+([^\\(]+)\\(");

  while (getline(statusStream, line)) {
    std::smatch match;
    if (std::regex_search(line, match, stateRegex)) {
      std::string state = match[1].str();  // Get the matched state string
      // Remove leading and trailing spaces using regex_replace
      state = std::regex_replace(state, std::regex("^\\s+|\\s+$"), "");

      if (state == "T") {
        std::cerr << "Process " << pid << " status is " << state
                  << " is in stopped state.\n";
        return true;
      } else {
        std::cout << "Process " << pid << " status is " << state
                  << " is not in stopped state.\n";
        return false;
      }
    }
  }
  return true;  // Error or process not found
}

/**
 * @brief Function to find the PID of a process by its name
 * @param [in] process_name  process name
 * @param [out] pid  process id
 * @return  true - find pid by process name; false - not find pid by process
 * name
 */
bool FindPidByProcessName(const std::string& process_name, pid_t& pid) {  //NOLINT
  DirCloser dir(opendir("/proc"));
  if (!dir.get()) {
    std::cerr << "Failed to open directory /proc" << std::endl;
    return false;
  }

  struct dirent* entry;
  while ((entry = readdir(dir.get())) != nullptr) {
    // Check if the entry is a directory and its name is numeric
    if (entry->d_type == DT_DIR) {
      std::string pid_str = entry->d_name;
      if (pid_str.find_first_not_of("0123456789") == std::string::npos) {
        // Read the "cmdline" file to get the process name
        std::string cmdline_path = "/proc/" + pid_str + "/cmdline";
        std::ifstream cmdline_file(cmdline_path);
        // FileCloser to manage cmdline_file
        FileCloser file_closer(cmdline_file);
        if (cmdline_file.is_open()) {
          std::stringstream cmdline_stream;
          cmdline_stream << cmdline_file.rdbuf();
          std::string cmdLine = cmdline_stream.str();
          // Check if the process name matches
          if (cmdLine.find(process_name) != std::string::npos) {
            pid = std::stoi(pid_str);
            // just return the first pid found
            return true;
          }
        } else {
          std::cerr << "Failed to open " << cmdline_path << std::endl;
        }
      }
    }
  }

  return false;
}

/**
 * @brief Function to try to resume a stopped process
 *
 * @param [in] attempts - if resume failed, try another attempts times.
 * @param [in] pid - pid find by process name.
 * @return true - Resume successfully; false - Resume failed
 */
bool TryToResumeProcessByPid(pid_t pid, int attempts) {
  for (int i = 0; i < attempts; ++i) {
    if (kill(pid, SIGCONT) == 0) {
      usleep(1000);  // sleep 1ms, wait for process status

      if (!IsProcessStopped(pid)) {
        std::cout << "Process " << pid << " resumed successfully.\n";
        return true;
      }
      usleep(1000 * 100);  // sleep 100ms, wait for another try
    }
  }
  std::cout << "Failed to resume process " << pid << " after " << attempts
            << " attempts.\n";
  return false;
}

/**
 * @brief Function to check if the process is not in stopped state
 *
 * @param [in] pid - pid find by process name.
 * @return true - Freeze successfully; false - Freeze failed
 */
bool FreezeProcessByPid(pid_t pid) {
  if (kill(pid, SIGSTOP) == -1) {
    std::cerr << "Failed to send SIGSTOP to process, pid" << pid << std::endl;
    return false;
  } else {
    std::cout << "Success to send SIGSTOP to process " << pid << std::endl;
  }
  usleep(1000);  // sleep 1ms, wait for process status
  if (IsProcessStopped(pid)) {
    std::cerr << "Process " << pid << " freeze success." << std::endl;
    return true;
  }
  return false;
}

/**
 * @brief Function to check if the process is not in stopped state
 *
 * @param [in] process_name - process name.
 * @return 0 - Freeze successfully;-1 - cannot found process;-2 - Freeze failed
 */
int FreezeProcessByName(const std::string& process_name) {
  pid_t pid;
  if (!FindPidByProcessName(process_name, pid)) {
    std::cerr << "Cannot find procass, " << process_name << std::endl;
    return -1;
  }
  if (!FreezeProcessByPid(pid)) {
    std::cerr << "process_name " << process_name << " pid " << pid
              << " freeze failed.\n";
    return -2;

  } else {
    std::cout << "process_name " << process_name << " pid " << pid
              << " freeze success.\n";
    return 0;
  }
}

/**
 * @brief Function to try to resume a stopped process
 *
 * @param [in] process_name - process name.
 * @param [in] attempts - if resume failed, try another attempts times.
 * @return 0 - successfully; -1 - not found process; -2 resume failed
 */
int TryToResumeProcessByName(const std::string& process_name, int attempts) {
  pid_t pid;
  if (!FindPidByProcessName(process_name, pid)) {
    std::cerr << "Cannot find process " << process_name << std::endl;
    return -1;
  }
  bool ret = TryToResumeProcessByPid(pid, attempts);
  if (!ret) {
    return -2;
  } else {
    return 0;
  }
}

/**
 * @brief terminate a process
 *
 * @param [in] process_name - process name.
 * @param [in] attempts - if resume failed, try another attempts times.
 * @return 0 -  successfully; -1 - not found process; -2 terminate failed
 *         -3 - Handle waitpid error
 */
int TerminateProcess(const std::string& process_name, int attempts) {
  pid_t pid;
  if (!FindPidByProcessName(process_name, pid)) {
    std::cerr << "Cannot find process " << process_name << std::endl;
    return -1;
  }

  for (int i = 0; i < attempts; ++i) {
    if (kill(pid, SIGKILL) == 0) {
      std::cout << "Process with PID: " << pid << " has been terminated.\n";
      int status;
      if (waitpid(pid, &status, 0) == -1) {
        std::cerr << "Error waiting for process " << pid << ": "
                  << strerror(errno) << std::endl;
        return -3;  // Handle waitpid error
      }
      return 0;
    } else {
      std::cerr << "Failed to kill process with PID: " << pid << std::endl;
    }
  }
  std::cerr << "Failed to kill process with PID: " << pid << " after "
            << attempts << " attempts.\n";
  return -2;  // Handle kill attempts failure
}
